#include <heightmap_bin.h>
#include <model.h>

#include <memory>
#include <cmath>
#include <iostream>
#include <fstream>

using namespace std;

heightmap_bin::heightmap_bin() : bin() {
}

heightmap_bin::~heightmap_bin() {
}

void
heightmap_bin::init(model *parent, pngstream *_pngs, int _level, int _offset_x, int _offset_y) {
  this->p = parent;
  this->pngs = _pngs;

  this->row_count = 0;

  this->level = _level;
  this->lcoef = (int) pow(2.0, (double) level);

  this->offset_x = _offset_x / lcoef;
  this->offset_y = _offset_y / lcoef;

  this->chbpp = (pngs->pngheader.bitdepth * pngs->pngheader.channels) / 8;
  this->colsize = p->hm_tile_size[0] * lcoef;
  this->rowsize = p->hm_tile_size[1] * lcoef;
  this->numtiles_x = pngs->pngheader.width / rowsize;

  this->rows = new unsigned char *[rowsize];
  this->tile = new unsigned char *[rowsize];
}

void 
heightmap_bin::addRow(unsigned char *row) {
  rows[row_count] = row;
  row_count++;

  if (row_count == p->hm_tile_size[1] * lcoef) {
    generateTiles();
  }
}

void
heightmap_bin::generateTiles() {
  // tile the input image rows data
  for (int i = 0; i != numtiles_x; i++) {
        
    for (int j = 0; j != rowsize; j++) {
      tile[j] = &rows[j][i * rowsize * chbpp];
    }

    unsigned char **scale_rows = new unsigned char *[p->hm_tile_size[1]];
    for (int j = 0; j != p->hm_tile_size[1]; j++) {
      scale_rows[j] = new unsigned char [p->hm_tile_size[0] * chbpp];
    }

    int s_y = pow(2.0, level);
    int s_x = pow(2.0, level);
    for (int y = 0; y < p->hm_tile_size[1]; y++) {
      for (int x = 0; x < p->hm_tile_size[0]; x++) {
        for (int b = 0; b != chbpp; b++) {
          scale_rows[y][x * chbpp + b] = tile[y * s_y][x * s_x * chbpp + b];
        }
      }
    }

    FILE *fout_scl = fopen("temp.png", "wb");

    pngstream *pngs_scale = new pngstream();
    pngs_scale->initializeOutput(fout_scl, 
                                 p->hm_tile_size[0],
                                 p->hm_tile_size[1],
                                 pngs->pngheader.bitdepth,
                                 pngs->pngheader.color_type);           

    pngs_scale->write(scale_rows);
    pngs_scale->finishOutput();

    for (int m = 0; m != p->hm_tile_size[1]; m++) {
      delete scale_rows[m];
    }
    delete scale_rows;
    scale_rows = NULL;

    fclose(fout_scl);

    // stream image to database
    std::fstream input_stream("temp.png",  fstream::in | fstream::binary);

    stringstream query;

    int id = (p->hm_size[0] / colsize) * offset_y + offset_x + i;
    int plcoef = (int) pow(2.0, (double) level + 1);
    int pcolsize = p->hm_tile_size[0] * plcoef;
    int parent_id = (p->hm_size[0] / pcolsize) * ((offset_y * lcoef) / plcoef) + ((offset_x + i) * lcoef) / plcoef;

    query << "INSERT INTO hm_tiles_" << level
          << " (id, fk_parent_hm_tile, data) VALUES (";
    if (level == (p->hm_levels - 1)) {
      query << id << ", NULL," << "?)";
    }
    else {
      query << id << "," << parent_id << "," << "?)";
    }

    try {
      std::auto_ptr<sql::PreparedStatement> prepstmt(p->getDB().prepareQuery(query.str()));
      prepstmt->setBlob(1, &input_stream);
      prepstmt->execute();
      prepstmt->close();
    }
    catch (SQLException &e) {
      cerr << "ERROR: " << e.what();
      cerr << " (MySQL error code: " << e.getErrorCode();
      cerr << ", SQLState: " << e.getSQLState() << ")" << endl;
    }

    input_stream.close();
  }

  for (int j = 0; j != row_count; j++) {
    delete [] rows[j];
    rows[j] = NULL;
  }

  row_count = 0;
  offset_y += 1;
}
